package com.stars.easyms.mq.service;

import com.stars.easyms.datasource.EasyMsMasterSlaveDataSource;
import com.stars.easyms.datasource.enums.DatabaseType;
import com.stars.easyms.datasource.holder.EasyMsMasterSlaveDataSourceHolder;
import com.stars.easyms.mq.asyn.AsynUpdateMQMessage;
import com.stars.easyms.mq.constant.MQConstants;
import com.stars.easyms.mq.dao.MQMessageDAO;
import com.stars.easyms.mq.dto.MQMessageDTO;
import com.stars.easyms.mq.dto.MQSendResult;
import com.stars.easyms.mq.properties.EasyMsMQProperties;
import com.stars.easyms.mq.util.EasyMsMQPackageUtil;
import com.stars.easyms.base.util.ApplicationContextHolder;
import com.stars.easyms.base.util.DateTimeUtil;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * MQ消息服务类
 *
 * @author guoguifang
 * @date 2018-04-23 13:54
 * @since 1.0.0
 */
public final class MQMessageService {

    private MQMessageDAO mysqlMQMessageDAO;

    private MQMessageDAO oracleMQMessageDAO;

    private AsynUpdateMQMessage asynUpdateMQMessage;

    public void updateMQMessageSendStatus(MQMessageDTO mqMessageDTO, @NonNull MQSendResult mqSendResult, String status, Long maxFailCount, boolean isQueueFull) {
        mqMessageDTO.setSendResult(mqSendResult.getSendResult());
        updateMQMessageStatus(mqMessageDTO, status, maxFailCount, isQueueFull);
    }

    public void updateMQMessageStatus(MQMessageDTO mqMessageDTO, String status, Long maxFailCount, boolean isQueueFull) {
        mqMessageDTO.setStatus(status);
        if (MQConstants.MQ_STATUS_FAIL_RETRY.equals(status) && !isQueueFull) {
            long failCount = mqMessageDTO.getFailCount();
            mqMessageDTO.setFailCount(++failCount);
            if (maxFailCount != null && failCount >= maxFailCount) {
                mqMessageDTO.setStatus(MQConstants.MQ_STATUS_FAIL);
                setMqMessageFinishTime(mqMessageDTO);
            }
        } else if (MQConstants.MQ_STATUS_SUCCESS.equals(status)) {
            setMqMessageFinishTime(mqMessageDTO);
        }
        getAsynUpdateMQMessage().add(mqMessageDTO);
    }

    public void batchUpdateMQMessageSendStatus(List<MQMessageDTO> mqMessageDTOList, @NonNull MQSendResult mqSendResult, String status, Long maxFailCount, boolean isQueueFull) {
        for (MQMessageDTO mqMessageDTO : mqMessageDTOList) {
            updateMQMessageSendStatus(mqMessageDTO, mqSendResult, status, maxFailCount, isQueueFull);
        }
    }

    public void batchUpdateMQMessageStatus(List<MQMessageDTO> mqMessageDTOList, String status, Long maxFailCount, boolean isQueueFull) {
        for (MQMessageDTO mqMessageDTO : mqMessageDTOList) {
            updateMQMessageStatus(mqMessageDTO, status, maxFailCount, isQueueFull);
        }
    }

    @NonNull
    public List<String> getMQMessageId(MQMessageDTO mqMessageDTO) {
        Map<String, Object> paramMap = new HashMap<>(2);
        paramMap.put("searchField", "messageIdField");
        paramMap.put("dto", mqMessageDTO);
        List<MQMessageDTO> resultList = getMQMessageDAO().getMQMessage(paramMap);
        if (CollectionUtils.isEmpty(resultList)) {
            return Collections.emptyList();
        }
        return resultList.stream().map(MQMessageDTO::getMessageId).collect(Collectors.toList());
    }

    @NonNull
    public List<MQMessageDTO> getMQMessage(MQMessageDTO mqMessageDTO) {
        Map<String, Object> paramMap = new HashMap<>(2);
        paramMap.put("searchField", "wholeMessageField");
        paramMap.put("dto", mqMessageDTO);
        return getMQMessageDAO().getMQMessage(paramMap);
    }

    @NonNull
    public List<String> getMQMessageId(MQMessageDTO mqMessageDTO, Timestamp startTime, Timestamp endTime) {
        Map<String, Object> paramMap = new HashMap<>(4);
        paramMap.put("searchField", "messageIdField");
        paramMap.put("dto", mqMessageDTO);
        paramMap.put("startTime", startTime);
        paramMap.put("endTime", endTime);
        List<MQMessageDTO> resultList = getMQMessageDAO().getMQMessage(paramMap);
        if (CollectionUtils.isEmpty(resultList)) {
            return Collections.emptyList();
        }
        return resultList.stream().map(MQMessageDTO::getMessageId).collect(Collectors.toList());
    }

    @NonNull
    public List<MQMessageDTO> getMQMessage(MQMessageDTO mqMessageDTO, Timestamp startTime, Timestamp endTime) {
        Map<String, Object> paramMap = new HashMap<>(4);
        paramMap.put("searchField", "wholeMessageField");
        paramMap.put("dto", mqMessageDTO);
        paramMap.put("startTime", startTime);
        paramMap.put("endTime", endTime);
        return getMQMessageDAO().getMQMessage(paramMap);
    }

    @NonNull
    public List<String> getMQMessageId(MQMessageDTO mqMessageDTO, List<String> messageIdList) {
        Map<String, Object> paramMap = new HashMap<>(4);
        paramMap.put("searchField", "messageIdField");
        paramMap.put("dto", mqMessageDTO);
        if (!CollectionUtils.isEmpty(messageIdList)) {
            paramMap.put("messageIdList", messageIdList);
        }
        List<MQMessageDTO> resultList = getMQMessageDAO().getMQMessage(paramMap);
        if (CollectionUtils.isEmpty(resultList)) {
            return Collections.emptyList();
        }
        return resultList.stream().map(MQMessageDTO::getMessageId).collect(Collectors.toList());
    }

    public Long getMQMessageCount(Map<String, Object> paramMap) {
        return getMQMessageDAO().getMQMessageCount(paramMap);
    }

    public List<MQMessageDTO> getMQMessageForFailRetry(Map<String, Object> paramMap) {
        return getMQMessageDAO().getMQMessageForFailRetry(paramMap);
    }

    public int insertMQMessageOldData(Map<String, Object> paramMap) {
        return getMQMessageDAO().insertMQMessageOldData(paramMap);
    }

    public void deleteMQMessageOldData(Map<String, Object> paramMap) {
        getMQMessageDAO().deleteMQMessageOldData(paramMap);
    }

    public String getId() {
        StringBuilder buffer = new StringBuilder();
        String random = ("000000000" + ThreadLocalRandom.current().nextInt(999999999));
        random = random.substring(random.length() - 9);
        return buffer.append(DateTimeUtil.getDatetimeNormalStrWithMills(System.currentTimeMillis())).append(random).toString();
    }

    public String getMessageId() {
        StringBuilder buffer = new StringBuilder();
        String random = ("000000000" + ThreadLocalRandom.current().nextInt(999999999));
        random = random.substring(random.length() - 9);
        return buffer.append("M").append(System.currentTimeMillis()).append(random).toString();
    }

    private void setMqMessageFinishTime(MQMessageDTO mqMessageDTO) {
        Timestamp finishTime = new Timestamp(System.currentTimeMillis());
        mqMessageDTO.setFinishTime(finishTime);
        mqMessageDTO.setUsedTime(finishTime.getTime() - mqMessageDTO.getFireTime().getTime());
    }

    private MQMessageDAO getMQMessageDAO() {
        EasyMsMasterSlaveDataSource easyMsMasterSlaveDataSource = EasyMsMasterSlaveDataSourceHolder.getMasterSlaveDataSource();
        if (easyMsMasterSlaveDataSource != null) {
            DatabaseType databaseType = easyMsMasterSlaveDataSource.getDatabaseType();
            if (databaseType == DatabaseType.ORACLE) {
                return getOracleMQMessageDAO();
            }
        } else if (EasyMsMQPackageUtil.getDefaultDbType() == DatabaseType.ORACLE) {
            return getOracleMQMessageDAO();
        }
        return getMysqlMQMessageDAO();
    }

    private MQMessageDAO getMysqlMQMessageDAO() {
        if (mysqlMQMessageDAO == null) {
            mysqlMQMessageDAO = (MQMessageDAO) ApplicationContextHolder.getBean("mysqlMQMessageDAO");
        }
        return mysqlMQMessageDAO;
    }

    private MQMessageDAO getOracleMQMessageDAO() {
        if (oracleMQMessageDAO == null) {
            oracleMQMessageDAO = (MQMessageDAO) ApplicationContextHolder.getBean("oracleMQMessageDAO");
        }
        return oracleMQMessageDAO;
    }

    /**
     * 获取异步修改MQ消息对象，采用懒加载的方式启动
     */
    private AsynUpdateMQMessage getAsynUpdateMQMessage() {
        AsynUpdateMQMessage localAsynUpdateMQMessage = asynUpdateMQMessage;
        if (localAsynUpdateMQMessage == null) {
            synchronized (this) {
                localAsynUpdateMQMessage = asynUpdateMQMessage;
                if (localAsynUpdateMQMessage == null) {
                    asynUpdateMQMessage = localAsynUpdateMQMessage = new AsynUpdateMQMessage();
                }
            }
        }
        return localAsynUpdateMQMessage;
    }

    public MQMessageService(EasyMsMQProperties easyMsMQProperties) {
        if (easyMsMQProperties.isEnabled()) {
            getAsynUpdateMQMessage();
        }
    }

}
